﻿// Xshell Execution Task
// 
// <copyright file="XsTask.cs" company="TaskyMedia LLC">
//   Copyright © 2010 TaskyMedia LLC
// </copyright>

namespace Xshell
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;

    using Xshell.Commands;

    /// <summary>
    /// Represents a Task to be executed by the Xshell engine.
    /// </summary>
    /// <remarks>
    /// A Task is defined as a command and any applicable set of arguments or parameters.
    /// </remarks>
    internal class XsTask
    {
        /// <summary>
        /// A cached list of reflected <see cref="IXsCommand"/> implementations.
        /// </summary>
        private static Dictionary<string, ReflectedCommandType> _commandLookupTable = new Dictionary<string, ReflectedCommandType>();

        /// <summary>
        /// Object used to synchronize changes to the Command types lookup table.
        /// </summary>
        private static object _tableLockObject = new object();

        /// <summary>
        /// The <see cref="IXsCommand" /> associated with the <see cref="XsTask"/>.
        /// </summary>
        private IXsCommand _command;

        /// <summary>
        /// The <see cref="IXsCommandArgs" /> associated with the <see cref="XsTask"/> (if applicable).
        /// </summary>
        private IXsCommandArgs _commandArgs;

        /// <summary>
        /// Initializes a new instance of the <see cref="XsTask"/> class.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="commandArgs">The command arguments.</param>
        public XsTask(IXsCommand command, IXsCommandArgs commandArgs)
        {
            this._command = command;
            this._commandArgs = commandArgs;
        }

        /// <summary>
        /// Creates a new instance of the <see cref="XsTask"/> class based on the specified command name.
        /// </summary>
        /// <param name="metadataCache">The metadata cache.</param>
        /// <param name="commandName">The name of the command.</param>
        /// <param name="args">The command arguments.</param>
        /// <returns>
        /// If a valid command could be process, returns a <see cref="XsTask"/> instance with the
        /// specified arguments; otherwise, returns null (Nothing in Visual Basic).
        /// </returns>
        public static XsTask CreateFromString(IMetadataCache metadataCache, string commandName, params XsArgument[] args)
        {
            IXsCommand command = null;
            IXsCommandArgs commandArgs = null;

            if (_commandLookupTable.Count == 0)
            {
                BuildCommandLookupTable(metadataCache);
            }

            // Try to create the command
            string commandNameCompare = commandName.ToLowerInvariant();
            ReflectedCommandType commandMetadata = null;
            if (_commandLookupTable.TryGetValue(commandNameCompare, out commandMetadata))
            {
                command = (IXsCommand)Activator.CreateInstance(commandMetadata.CommandType);
                if (commandMetadata.CommandArgsType != null)
                {
                    commandArgs = (IXsCommandArgs)Activator.CreateInstance(commandMetadata.CommandArgsType);
                }
            }

            // If any arguments were specified, attempt to assign them
            if (args.Length > 0 && commandArgs != null)
            {
                // Build a list of properties for the type
                Dictionary<string, PropertyInfo> typeProperties = new Dictionary<string, PropertyInfo>();
                Type argsType = commandArgs.GetType();
                foreach (PropertyInfo propertyInfo in argsType.GetProperties())
                {
                    typeProperties.Add(propertyInfo.Name.ToLowerInvariant(), propertyInfo);
                }

                // Assign all properties passed in to args
                foreach (XsArgument arg in args)
                {
                    PropertyInfo propertyInfo = null;
                    if (typeProperties.TryGetValue(arg.Name.ToLowerInvariant(), out propertyInfo))
                    {
                        propertyInfo.SetValue(commandArgs, arg.Value, null);
                    }
                }

                typeProperties.Clear();
            }

            // Return a command if it was found
            if (command != null)
            {
                return new XsTask(command, commandArgs);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Executes the task.
        /// </summary>
        public void Execute()
        {
            if (this._command != null)
            {
                this._command.ExecuteCommand(this._commandArgs);
            }
        }

        /// <summary>
        /// Builds the command types lookup table.
        /// </summary>
        /// <param name="metadataCache">The metadata cache.</param>
        private static void BuildCommandLookupTable(IMetadataCache metadataCache)
        {
            lock (_tableLockObject)
            {
                if (_commandLookupTable.Count == 0)
                {
                    // Build a list of command types
                    foreach (Type type in metadataCache.GetCachedTypes())
                    {
                        if (!type.IsInterface && typeof(IXsCommand).IsAssignableFrom(type))
                        {
                            object[] attributes = type.GetCustomAttributes(typeof(XsCommandAttribute), true);
                            if (attributes != null)
                            {
                                XsCommandAttribute attribute = (XsCommandAttribute)attributes[0];
                                _commandLookupTable.Add(attribute.Name.ToLowerInvariant(), new ReflectedCommandType(type, attribute.CommandArgsContainerType));
                            }
                        }
                    }
                }
            }
        }
    }
}
